package com.jedis.clients.commands;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import redis.clients.jedis.Client;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisDataException;

public class ListCommandsTest extends JedisCommandTestBase {
    final byte[] bfoo = { 0x01, 0x02, 0x03, 0x04 };
    final byte[] bbar = { 0x05, 0x06, 0x07, 0x08 };
    final byte[] bcar = { 0x09, 0x0A, 0x0B, 0x0C };
    final byte[] bA = { 0x0A };
    final byte[] bB = { 0x0B };
    final byte[] bC = { 0x0C };
    final byte[] b1 = { 0x01 };
    final byte[] b2 = { 0x02 };
    final byte[] b3 = { 0x03 };
    final byte[] bhello = { 0x04, 0x02 };
    final byte[] bx = { 0x02, 0x04 };
    final byte[] bdst = { 0x11, 0x12, 0x13, 0x14 };

    @Test
    public void rpush() {
        long size = jedis.rpush("foo", "bar");
        assertEquals(1, size);
        size = jedis.rpush("foo", "foo");
        assertEquals(2, size);

        // Binary
        long bsize = jedis.rpush(bfoo, bbar);
        assertEquals(1, bsize);
        bsize = jedis.rpush(bfoo, bfoo);
        assertEquals(2, bsize);

    }

    @Test
    public void lpush() {
        long size = jedis.lpush("foo", "bar");
        assertEquals(1, size);
        size = jedis.lpush("foo", "foo");
        assertEquals(2, size);

        // Binary
        long bsize = jedis.lpush(bfoo, bbar);
        assertEquals(1, bsize);
        bsize = jedis.lpush(bfoo, bfoo);
        assertEquals(2, bsize);

    }

    @Test
    public void llen() {
        assertEquals(0, jedis.llen("foo").intValue());
        jedis.lpush("foo", "bar");
        jedis.lpush("foo", "car");
        assertEquals(2, jedis.llen("foo").intValue());

        // Binary
        assertEquals(0, jedis.llen(bfoo).intValue());
        jedis.lpush(bfoo, bbar);
        jedis.lpush(bfoo, bcar);
        assertEquals(2, jedis.llen(bfoo).intValue());

    }

    @Test
    public void llenNotOnList() {
        try {
            jedis.set("foo", "bar");
            jedis.llen("foo");
            fail("JedisDataException expected");
        } catch (final JedisDataException e) {
        }

        // Binary
        try {
            jedis.set(bfoo, bbar);
            jedis.llen(bfoo);
            fail("JedisDataException expected");
        } catch (final JedisDataException e) {
        }

    }

    @Test
    public void lrange() {
        jedis.rpush("foo", "a");
        jedis.rpush("foo", "b");
        jedis.rpush("foo", "c");

        List<String> expected = new ArrayList<String>();
        expected.add("a");
        expected.add("b");
        expected.add("c");

        List<String> range = jedis.lrange("foo", 0, 2);
        assertEquals(expected, range);

        range = jedis.lrange("foo", 0, 20);
        assertEquals(expected, range);

        expected = new ArrayList<String>();
        expected.add("b");
        expected.add("c");

        range = jedis.lrange("foo", 1, 2);
        assertEquals(expected, range);

        expected = new ArrayList<String>();
        range = jedis.lrange("foo", 2, 1);
        assertEquals(expected, range);

        // Binary
        jedis.rpush(bfoo, bA);
        jedis.rpush(bfoo, bB);
        jedis.rpush(bfoo, bC);

        List<byte[]> bexpected = new ArrayList<byte[]>();
        bexpected.add(bA);
        bexpected.add(bB);
        bexpected.add(bC);

        List<byte[]> brange = jedis.lrange(bfoo, 0, 2);
        assertEquals(bexpected, brange);

        brange = jedis.lrange(bfoo, 0, 20);
        assertEquals(bexpected, brange);

        bexpected = new ArrayList<byte[]>();
        bexpected.add(bB);
        bexpected.add(bC);

        brange = jedis.lrange(bfoo, 1, 2);
        assertEquals(bexpected, brange);

        bexpected = new ArrayList<byte[]>();
        brange = jedis.lrange(bfoo, 2, 1);
        assertEquals(bexpected, brange);

    }

    @Test
    public void ltrim() {
        jedis.lpush("foo", "1");
        jedis.lpush("foo", "2");
        jedis.lpush("foo", "3");
        String status = jedis.ltrim("foo", 0, 1);

        List<String> expected = new ArrayList<String>();
        expected.add("3");
        expected.add("2");

        assertEquals("OK", status);
        assertEquals(2, jedis.llen("foo").intValue());
        assertEquals(expected, jedis.lrange("foo", 0, 100));

        // Binary
        jedis.lpush(bfoo, b1);
        jedis.lpush(bfoo, b2);
        jedis.lpush(bfoo, b3);
        String bstatus = jedis.ltrim(bfoo, 0, 1);

        List<byte[]> bexpected = new ArrayList<byte[]>();
        bexpected.add(b3);
        bexpected.add(b2);

        assertEquals("OK", bstatus);
        assertEquals(2, jedis.llen(bfoo).intValue());
        assertEquals(bexpected, jedis.lrange(bfoo, 0, 100));

    }

    @Test
    public void lindex() {
        jedis.lpush("foo", "1");
        jedis.lpush("foo", "2");
        jedis.lpush("foo", "3");

        List<String> expected = new ArrayList<String>();
        expected.add("3");
        expected.add("bar");
        expected.add("1");

        String status = jedis.lset("foo", 1, "bar");

        assertEquals("OK", status);
        assertEquals(expected, jedis.lrange("foo", 0, 100));

        // Binary
        jedis.lpush(bfoo, b1);
        jedis.lpush(bfoo, b2);
        jedis.lpush(bfoo, b3);

        List<byte[]> bexpected = new ArrayList<byte[]>();
        bexpected.add(b3);
        bexpected.add(bbar);
        bexpected.add(b1);

        String bstatus = jedis.lset(bfoo, 1, bbar);

        assertEquals("OK", bstatus);
        assertEquals(bexpected, jedis.lrange(bfoo, 0, 100));
    }

    @Test
    public void lset() {
        jedis.lpush("foo", "1");
        jedis.lpush("foo", "2");
        jedis.lpush("foo", "3");

        assertEquals("3", jedis.lindex("foo", 0));
        assertEquals(null, jedis.lindex("foo", 100));

        // Binary
        jedis.lpush(bfoo, b1);
        jedis.lpush(bfoo, b2);
        jedis.lpush(bfoo, b3);

        assertArrayEquals(b3, jedis.lindex(bfoo, 0));
        assertEquals(null, jedis.lindex(bfoo, 100));

    }

    @Test
    public void lrem() {
        jedis.lpush("foo", "hello");
        jedis.lpush("foo", "hello");
        jedis.lpush("foo", "x");
        jedis.lpush("foo", "hello");
        jedis.lpush("foo", "c");
        jedis.lpush("foo", "b");
        jedis.lpush("foo", "a");

        long count = jedis.lrem("foo", -2, "hello");

        List<String> expected = new ArrayList<String>();
        expected.add("a");
        expected.add("b");
        expected.add("c");
        expected.add("hello");
        expected.add("x");

        assertEquals(2, count);
        assertEquals(expected, jedis.lrange("foo", 0, 1000));
        assertEquals(0, jedis.lrem("bar", 100, "foo").intValue());

        // Binary
        jedis.lpush(bfoo, bhello);
        jedis.lpush(bfoo, bhello);
        jedis.lpush(bfoo, bx);
        jedis.lpush(bfoo, bhello);
        jedis.lpush(bfoo, bC);
        jedis.lpush(bfoo, bB);
        jedis.lpush(bfoo, bA);

        long bcount = jedis.lrem(bfoo, -2, bhello);

        List<byte[]> bexpected = new ArrayList<byte[]>();
        bexpected.add(bA);
        bexpected.add(bB);
        bexpected.add(bC);
        bexpected.add(bhello);
        bexpected.add(bx);

        assertEquals(2, bcount);
        assertEquals(bexpected, jedis.lrange(bfoo, 0, 1000));
        assertEquals(0, jedis.lrem(bbar, 100, bfoo).intValue());

    }

    @Test
    public void lpop() {
        jedis.rpush("foo", "a");
        jedis.rpush("foo", "b");
        jedis.rpush("foo", "c");

        String element = jedis.lpop("foo");
        assertEquals("a", element);

        List<String> expected = new ArrayList<String>();
        expected.add("b");
        expected.add("c");

        assertEquals(expected, jedis.lrange("foo", 0, 1000));
        jedis.lpop("foo");
        jedis.lpop("foo");

        element = jedis.lpop("foo");
        assertEquals(null, element);

        // Binary
        jedis.rpush(bfoo, bA);
        jedis.rpush(bfoo, bB);
        jedis.rpush(bfoo, bC);

        byte[] belement = jedis.lpop(bfoo);
        assertArrayEquals(bA, belement);

        List<byte[]> bexpected = new ArrayList<byte[]>();
        bexpected.add(bB);
        bexpected.add(bC);

        assertEquals(bexpected, jedis.lrange(bfoo, 0, 1000));
        jedis.lpop(bfoo);
        jedis.lpop(bfoo);

        belement = jedis.lpop(bfoo);
        assertEquals(null, belement);

    }

    @Test
    public void rpop() {
        jedis.rpush("foo", "a");
        jedis.rpush("foo", "b");
        jedis.rpush("foo", "c");

        String element = jedis.rpop("foo");
        assertEquals("c", element);

        List<String> expected = new ArrayList<String>();
        expected.add("a");
        expected.add("b");

        assertEquals(expected, jedis.lrange("foo", 0, 1000));
        jedis.rpop("foo");
        jedis.rpop("foo");

        element = jedis.rpop("foo");
        assertEquals(null, element);

        // Binary
        jedis.rpush(bfoo, bA);
        jedis.rpush(bfoo, bB);
        jedis.rpush(bfoo, bC);

        byte[] belement = jedis.rpop(bfoo);
        assertArrayEquals(bC, belement);

        List<byte[]> bexpected = new ArrayList<byte[]>();
        bexpected.add(bA);
        bexpected.add(bB);

        assertEquals(bexpected, jedis.lrange(bfoo, 0, 1000));
        jedis.rpop(bfoo);
        jedis.rpop(bfoo);

        belement = jedis.rpop(bfoo);
        assertEquals(null, belement);

    }

    @Test
    public void rpoplpush() {
        jedis.rpush("foo", "a");
        jedis.rpush("foo", "b");
        jedis.rpush("foo", "c");

        jedis.rpush("dst", "foo");
        jedis.rpush("dst", "bar");

        String element = jedis.rpoplpush("foo", "dst");

        assertEquals("c", element);

        List<String> srcExpected = new ArrayList<String>();
        srcExpected.add("a");
        srcExpected.add("b");

        List<String> dstExpected = new ArrayList<String>();
        dstExpected.add("c");
        dstExpected.add("foo");
        dstExpected.add("bar");

        assertEquals(srcExpected, jedis.lrange("foo", 0, 1000));
        assertEquals(dstExpected, jedis.lrange("dst", 0, 1000));

        // Binary
        jedis.rpush(bfoo, bA);
        jedis.rpush(bfoo, bB);
        jedis.rpush(bfoo, bC);

        jedis.rpush(bdst, bfoo);
        jedis.rpush(bdst, bbar);

        byte[] belement = jedis.rpoplpush(bfoo, bdst);

        assertArrayEquals(bC, belement);

        List<byte[]> bsrcExpected = new ArrayList<byte[]>();
        bsrcExpected.add(bA);
        bsrcExpected.add(bB);

        List<byte[]> bdstExpected = new ArrayList<byte[]>();
        bdstExpected.add(bC);
        bdstExpected.add(bfoo);
        bdstExpected.add(bbar);

        assertEquals(bsrcExpected, jedis.lrange(bfoo, 0, 1000));
        assertEquals(bdstExpected, jedis.lrange(bdst, 0, 1000));

    }

    @Test
    public void blpop() throws InterruptedException {
        List<String> result = jedis.blpop(1, "foo");
        assertNull(result);

        new Thread(new Runnable() {
            public void run() {
                try {
                    Jedis j = createJedis();
                    j.lpush("foo", "bar");
                    j.disconnect();
                } catch (Exception ex) {
                    fail(ex.getMessage());
                }
            }
        }).start();

        result = jedis.blpop(1, "foo");
        assertNotNull(result);
        assertEquals(2, result.size());
        assertEquals("foo", result.get(0));
        assertEquals("bar", result.get(1));

        // Binary
        List<byte[]> bresult = jedis.blpop(1, bfoo);
        assertNull(bresult);

        new Thread(new Runnable() {
            public void run() {
                try {
                    Jedis j = createJedis();
                    j.lpush(bfoo, bbar);
                    j.disconnect();
                } catch (Exception ex) {
                    fail(ex.getMessage());
                }
            }
        }).start();

        bresult = jedis.blpop(1, bfoo);
        assertNotNull(bresult);
        assertEquals(2, bresult.size());
        assertArrayEquals(bfoo, bresult.get(0));
        assertArrayEquals(bbar, bresult.get(1));

    }

    @Test
    public void brpop() throws InterruptedException {
        List<String> result = jedis.brpop(1, "foo");
        assertNull(result);

        new Thread(new Runnable() {
            public void run() {
                try {
                    Jedis j = createJedis();
                    j.lpush("foo", "bar");
                    j.disconnect();
                } catch (Exception ex) {
                    fail(ex.getMessage());
                }
            }
        }).start();

        result = jedis.brpop(1, "foo");
        assertNotNull(result);
        assertEquals(2, result.size());
        assertEquals("foo", result.get(0));
        assertEquals("bar", result.get(1));

        // Binary
        List<byte[]> bresult = jedis.brpop(1, bfoo);
        assertNull(bresult);

        new Thread(new Runnable() {
            public void run() {
                try {
                    Jedis j = createJedis();
                    j.lpush(bfoo, bbar);
                    j.disconnect();
                } catch (Exception ex) {
                    fail(ex.getMessage());
                }
            }
        }).start();

        bresult = jedis.brpop(1, bfoo);
        assertNotNull(bresult);
        assertEquals(2, bresult.size());
        assertArrayEquals(bfoo, bresult.get(0));
        assertArrayEquals(bbar, bresult.get(1));

    }

    @Test
    public void lpushx() {
        long status = jedis.lpushx("foo", "bar");
        assertEquals(0, status);

        jedis.lpush("foo", "a");
        status = jedis.lpushx("foo", "b");
        assertEquals(2, status);

        // Binary
        long bstatus = jedis.lpushx(bfoo, bbar);
        assertEquals(0, bstatus);

        jedis.lpush(bfoo, bA);
        bstatus = jedis.lpushx(bfoo, bB);
        assertEquals(2, bstatus);

    }

    @Test
    public void rpushx() {
        long status = jedis.rpushx("foo", "bar");
        assertEquals(0, status);

        jedis.lpush("foo", "a");
        status = jedis.rpushx("foo", "b");
        assertEquals(2, status);

        // Binary
        long bstatus = jedis.rpushx(bfoo, bbar);
        assertEquals(0, bstatus);

        jedis.lpush(bfoo, bA);
        bstatus = jedis.rpushx(bfoo, bB);
        assertEquals(2, bstatus);
    }

    @Test
    public void linsert() {
        long status = jedis.linsert("foo", Client.LIST_POSITION.BEFORE, "bar",
                "car");
        assertEquals(0, status);

        jedis.lpush("foo", "a");
        status = jedis.linsert("foo", Client.LIST_POSITION.AFTER, "a", "b");
        assertEquals(2, status);

        List<String> actual = jedis.lrange("foo", 0, 100);
        List<String> expected = new ArrayList<String>();
        expected.add("a");
        expected.add("b");

        assertEquals(expected, actual);

        status = jedis
                .linsert("foo", Client.LIST_POSITION.BEFORE, "bar", "car");
        assertEquals(-1, status);

        // Binary
        long bstatus = jedis.linsert(bfoo, Client.LIST_POSITION.BEFORE, bbar,
                bcar);
        assertEquals(0, bstatus);

        jedis.lpush(bfoo, bA);
        bstatus = jedis.linsert(bfoo, Client.LIST_POSITION.AFTER, bA, bB);
        assertEquals(2, bstatus);

        List<byte[]> bactual = jedis.lrange(bfoo, 0, 100);
        List<byte[]> bexpected = new ArrayList<byte[]>();
        bexpected.add(bA);
        bexpected.add(bB);

        assertEquals(bexpected, bactual);

        bstatus = jedis.linsert(bfoo, Client.LIST_POSITION.BEFORE, bbar, bcar);
        assertEquals(-1, bstatus);

    }

    @Test
    public void brpoplpush() {
        (new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(2000);
                    Jedis j = createJedis();
                    j.lpush("foo", "a");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        })).start();

        String element = jedis.brpoplpush("foo", "bar", 0);

        assertEquals("a", element);
        assertEquals(1, jedis.llen("bar").longValue());
        assertEquals("a", jedis.lrange("bar", 0, -1).get(0));

        (new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(100);
                    Jedis j = createJedis();
                    j.lpush("foo", "a");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        })).start();

        byte[] brpoplpush = jedis.brpoplpush("foo".getBytes(),
                "bar".getBytes(), 0);

        assertTrue(Arrays.equals("a".getBytes(), brpoplpush));
        assertEquals(1, jedis.llen("bar").longValue());
        assertEquals("a", jedis.lrange("bar", 0, -1).get(0));

    }
}